#region License
/*  Pvax.Utils
    Copyright (c) 2006, Alexey A. Popov
    All rights reserved.

    Redistribution and use in source and binary forms, with or without modification, are
    permitted provided that the following conditions are met:

    - Redistributions of source code must retain the above copyright notice, this list
      of conditions and the following disclaimer.

    - Redistributions in binary form must reproduce the above copyright notice, this list
      of conditions and the following disclaimer in the documentation and/or other materials
      provided with the distribution.

    - Neither the name of the Alexey A. Popov nor the names of its contributors may be used to
      endorse or promote products derived from this software without specific prior written
      permission.

    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS *AS IS* AND ANY EXPRESS
    OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
    AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
    CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
    DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
    DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
    IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
    OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#endregion

using System;
using System.Collections;
using System.ComponentModel;
using System.Threading;

namespace Pvax.Utils
{
	/// <summary>
	/// Represents a simple implementation of <see cref="ISynchronizeInvoke"/>
	/// interface.
	/// </summary>
	public class Synchronizer: ISynchronizeInvoke, IDisposable
	{
		private class State: IAsyncResult
		{
			Synchronizer synchronizer;

			Delegate handler;

			object[] arguments;

			internal object Result;

			bool complete;

			AutoResetEvent waitHandle;

			// Initialize a new instance
			internal State(Synchronizer synchronizer, Delegate handler, object[] arguments)
			{
				this.synchronizer = synchronizer;
				this.waitHandle = new AutoResetEvent(false);
				Init(handler, arguments);
			}

			// Initialize an existing instance from the pool
			internal void Init(Delegate handler, object[] arguments)
			{
				this.arguments = arguments;
				this.handler = handler;
			}

			// Release the instance before putting it back to the pool
			// Note that the fields associated with the delegate are set to null
			// or the objects the delegate points at and the delegate parameters
			// might hang in memory for an unknown amount of time
			internal void Clear()
			{
				this.arguments = null;
				this.handler = null;
				this.Result = null;
			}

			// Release managed resources and dispose of the object
			internal void Done()
			{
				waitHandle.Close();
				waitHandle = null;
			}

			internal void Invoke()
			{
				try
				{
					// NB: System.Windows.Forms.Control also checks for MethodInvokerDelegate
					// I don't want to reference a whole fat assembly for console applications
					if(typeof(EventHandler) == handler.GetType())
						((EventHandler)handler)(arguments[0], EventArgs.Empty);
					else
						Result = handler.DynamicInvoke(arguments);
				}
				finally
				{
					waitHandle.Set();
					synchronizer.ReleaseState(this);
				}
				complete = true;
			}

			#region IAsyncResult interface implementation
			object IAsyncResult.AsyncState
			{
				get
				{
					return null;
				}
			}

			WaitHandle IAsyncResult.AsyncWaitHandle
			{
				get
				{
					return waitHandle;
				}
			}

			bool IAsyncResult.CompletedSynchronously
			{
				get
				{
					return false;
				}
			}

			bool IAsyncResult.IsCompleted
			{
				get
				{
					return complete;
				}
			}
			#endregion
		}

		// Marshalled delegates queue
		readonly Queue queue;

		readonly int mainThreadId;

		// State objects pool
		ArrayList statePool;

		/// <summary>
		/// Initializes a new instance of the <see cref="Synchronizer"/>.
		/// </summary>
		public Synchronizer()
		{
			queue = new Queue();
			mainThreadId = Thread.CurrentThread.ManagedThreadId;
			statePool = new ArrayList();
		}

		#region State pool management
		State AcquireState(Delegate handler, object[] arguments)
		{
			State result;
			lock(statePool.SyncRoot)
			{
				if(statePool.Count > 0)
				{
					int index = statePool.Count - 1;
					result = (State)statePool[index];
					statePool.RemoveAt(index);
					result.Init(handler, arguments);
				}
				else
					result = new State(this, handler, arguments);
			}
			return result;
		}

		void ReleaseState(State state)
		{
			lock(statePool.SyncRoot)
				statePool.Add(state);
		}
		#endregion

		#region IDisposable interface implementation
		void CheckDisposed()
		{
			if(null == statePool)
				throw new ObjectDisposedException("Synchronizer");
		}

		/// <summary>
		/// Releases the resources used by the <see cref="Synchronizer"/>
		/// object.
		/// </summary>
		public void Dispose()
		{
			if(null != statePool)
			{
				lock(statePool.SyncRoot)
				{
					if(null != statePool)
					{
						foreach(State state in statePool)
							state.Done();
						statePool = null;
					}
				}
			}
		}
		#endregion

		#region ISynchronizeInvoke interface implementation
		/// <summary>
		/// Gets a value indicating whether the caller must call an invoke
		/// method when making method calls.
		/// </summary>
		/// <value>
		/// <c>true</c> if the delegate's target is on a different thread than
		/// the calling thread; otherwise, <c>false</c>.
		/// </value>
		public bool InvokeRequired
		{
			get
			{
				CheckDisposed();
				return mainThreadId != Thread.CurrentThread.ManagedThreadId;
			}
		}

		/// <summary>
		/// Executes the specified delegate asynchronously with the specified
		/// arguments, on the main application thread.
		/// </summary>
		/// <param name="method">
		/// A delegate to a method that takes parameters of the same number and
		/// type that are contained in the <paramref name="args"/> parameter.
		/// </param>
		/// <param name="args">
		/// An array of objects to pass as arguments to the given method. This
		/// can be <c>null</c> if no arguments are needed.
		/// </param>
		/// <returns>
		/// An <see cref="IAsyncResult"/> object that represents the result of
		/// the <c>BeginInvoke</c> operation.
		/// </returns>
		public IAsyncResult BeginInvoke(Delegate method, object[] args)
		{
			CheckDisposed();
			if(null == method)
				throw new ArgumentNullException("method");
			State mc = AcquireState(method, args);
			lock(queue.SyncRoot)
				queue.Enqueue(mc);
			return mc;
		}

		/// <summary>
		/// Retrieves the return value of the asynchronous operation
		/// represented by the <see cref="IAsyncResult"/> object passed.
		/// </summary>
		/// <param name="asyncResult">
		/// The <see cref="IAsyncResult"/> object that represents a specific
		/// invoke asynchronous operation, returned when calling
		/// <see cref="BeginInvoke"/>.
		/// </param>
		/// <returns>
		/// The <see cref="Object"/> generated by the asynchronous operation.
		/// </returns>
		public object EndInvoke(IAsyncResult asyncResult)
		{
			CheckDisposed();
			if(null == asyncResult)
				throw new ArgumentNullException("asyncResult");
			asyncResult.AsyncWaitHandle.WaitOne();
			State mc = (State)asyncResult;
			mc.Clear();
			return mc.Result;
		}

		/// <summary>
		/// Executes the specified delegate with the specified arguments, on
		/// the main application thread.
		/// </summary>
		/// <param name="method">
		/// A delegate to a method that takes parameters of the same number and
		/// type that are contained in the <paramref name="args"/> parameter.
		/// </param>
		/// <param name="args">
		/// An array of objects to pass as arguments to the given method. This
		/// can be <c>null</c> if no arguments are needed.
		/// </param>
		/// <returns>
		/// An <see cref="IAsyncResult"/> object that represents the result of
		/// the <c>BeginInvoke</c> operation.
		/// </returns>
		public object Invoke(Delegate method, object[] args)
		{
			return EndInvoke(BeginInvoke(method, args));
		}
		#endregion

		/// <summary>
		/// Raises the pending events synchronousely in the context of
		/// the current thread.
		/// </summary>
		public void Poll()
		{
			CheckDisposed();
			lock(queue.SyncRoot)
			{
				while(queue.Count > 0)
				{
					State mc = (State)queue.Dequeue();
					mc.Invoke();
				}
			}
		}
	}
}
